/*
 * Playlist Object for the jPlayer Plugin http://www.jplayer.org Copyright (c)
 * 2009 - 2011 Happyworm Ltd Dual licensed under the MIT and GPL licenses. -
 * http://www.opensource.org/licenses/mit-license.php -
 * http://www.gnu.org/copyleft/gpl.html Author: Mark J Panaghiston Version:
 * 2.1.0 (jPlayer 2.1.0) Date: 1st September 2011
 */

/* Code verified using http://www.jshint.com/ */
/*
 * jshint asi:false, bitwise:false, boss:false, browser:true, curly:true,
 * debug:false, eqeqeq:true, eqnull:false, evil:false, forin:false, immed:false,
 * jquery:true, laxbreak:false, newcap:true, noarg:true, noempty:true,
 * nonew:true, nomem:false, onevar:false, passfail:false, plusplus:false,
 * regexp:false, undef:true, sub:false, strict:false, white:false
 */
/* global jPlayerPlaylist: true, jQuery:false, alert:false */

(function($, undefined) {
	
	jPlayerPlaylist = function(cssSelector, playlist, options) {
		var self = this;
		
		this.current = 0;
		this.loop = false; // Flag used with the jPlayer repeat event
		this.shuffled = false;
		this.removing = false; // Flag is true during remove animation,
		// disabling the remove() method until complete.
		
		this.cssSelector = $.extend({}, this._cssSelector, cssSelector); // Object:
		// Containing
		// the
		// css
		// selectors
		// for
		// jPlayer
		// and
		// its
		// cssSelectorAncestor
		this.options = $.extend(true, {}, this._options, options); // Object:
		// The
		// jPlayer
		// constructor
		// options
		// for this
		// playlist
		// and the
		// playlist
		// options
		
		this.playlist = []; // Array of Objects: The current playlist displayed
		// (Un-shuffled or Shuffled)
		this.original = []; // Array of Objects: The original playlist
		
		this._initPlaylist(playlist); // Copies playlist to this.original.
		// Then mirrors this.original to
		// this.playlist. Creating two arrays,
		// where the element pointers match.
		// (Enables pointer comparison.)
		
		// Setup the css selectors for the extra interface items used by the
		// playlist.
		this.cssSelector.title = this.cssSelector.cssSelectorAncestor + " .jp-title"; // Note
		// that
		// the
		// text
		// is
		// written
		// to
		// the
		// decendant
		// li
		// node.
		this.cssSelector.playlist = this.cssSelector.cssSelectorAncestor + " .jp-playlist";
		this.cssSelector.next = this.cssSelector.cssSelectorAncestor + " .jp-next";
		this.cssSelector.previous = this.cssSelector.cssSelectorAncestor + " .jp-previous";
		this.cssSelector.shuffle = this.cssSelector.cssSelectorAncestor + " .jp-shuffle";
		this.cssSelector.shuffleOff = this.cssSelector.cssSelectorAncestor + " .jp-shuffle-off";
		
		// Override the cssSelectorAncestor given in options
		this.options.cssSelectorAncestor = this.cssSelector.cssSelectorAncestor;
		
		// Override the default repeat event handler
		this.options.repeat = function(event) {
			self.loop = event.jPlayer.options.loop;
		};
		
		// Create a ready event handler to initialize the playlist
		$(this.cssSelector.jPlayer).bind($.jPlayer.event.ready, function(event) {
			self._init();
		});
		
		// Create an ended event handler to move to the next item
		$(this.cssSelector.jPlayer).bind($.jPlayer.event.ended, function(event) {
			self.next();
		});
		
		// Create a play event handler to pause other instances
		$(this.cssSelector.jPlayer).bind($.jPlayer.event.play, function(event) {
			$(this).jPlayer("pauseOthers");
		});
		
		// Create a resize event handler to show the title in full screen mode.
		$(this.cssSelector.jPlayer).bind($.jPlayer.event.resize, function(event) {
			if (event.jPlayer.options.fullScreen) {
				$(self.cssSelector.title).show();
			} else {
				$(self.cssSelector.title).hide();
			}
		});
		
		// Create click handlers for the extra buttons that do playlist
		// functions.
		$(this.cssSelector.previous).click(function() {
			self.previous();
			$(this).blur();
			return false;
		});
		
		$(this.cssSelector.next).click(function() {
			self.next();
			$(this).blur();
			return false;
		});
		
		$(this.cssSelector.shuffle).click(function() {
			self.shuffle(true);
			return false;
		});
		$(this.cssSelector.shuffleOff).click(function() {
			self.shuffle(false);
			return false;
		}).hide();
		
		// Put the title in its initial display state
		if (!this.options.fullScreen) {
			$(this.cssSelector.title).hide();
		}
		
		// Remove the empty <li> from the page HTML. Allows page to be valid
		// HTML, while not interfereing with display animations
		$(this.cssSelector.playlist + " ul").empty();
		
		// Create .live() handlers for the playlist items along with the free
		// media and remove controls.
		this._createItemHandlers();
		
		// Instance jPlayer
		$(this.cssSelector.jPlayer).jPlayer(this.options);
	};
	
	jPlayerPlaylist.prototype = {
	    _cssSelector: { // static object, instanced in constructor
	        jPlayer: "#jquery_jplayer_1",
	        cssSelectorAncestor: "#jp_container_1"
	    },
	    _options: { // static object, instanced in constructor
		    playlistOptions: {
		        autoPlay: true,
		        loopOnPrevious: false,
		        shuffleOnLoop: true,
		        enableRemoveControls: false,
		        displayTime: 'slow',
		        addTime: 'fast',
		        removeTime: 'fast',
		        shuffleTime: 'slow',
		        itemClass: "jp-playlist-item",
		        freeGroupClass: "jp-free-media",
		        freeItemClass: "jp-playlist-item-free",
		        removeItemClass: "jp-playlist-item-remove"
		    }
	    },
	    _getDisplayedIndex: function(index) {
		    return this.playlist.length - 1 - index;
	    },
	    option: function(option, value) { // For changing playlist options
		    // only
		    if (value === undefined) {
			    return this.options.playlistOptions[option];
		    }
		    
		    this.options.playlistOptions[option] = value;
		    
		    switch (option) {
		    case "enableRemoveControls":
			    this._updateControls();
			    break;
		    case "itemClass":
		    case "freeGroupClass":
		    case "freeItemClass":
		    case "removeItemClass":
			    this._refresh(true); // Instant
			    this._createItemHandlers();
			    break;
		    }
		    return this;
	    },
	    _init: function(instant) {
		    var self = this;
		    if (instant) {
			    this._refresh(true);
			    self.play(self.current);
		    } else {
			    this._refresh(function() {
				    if (self.options.playlistOptions.autoPlay) {
					    self.play(self.current);
				    } else {
					    self.select(self.current);
				    }
			    });
		    }
	    },
	    _initPlaylist: function(playlist) {
		    this.current = 0;
		    this.shuffled = false;
		    this.removing = false;
		    this.original = $.extend(true, [], playlist); // Copy the Array of
		    // Objects
		    this._originalPlaylist();
	    },
	    _originalPlaylist: function() {
		    var self = this;
		    this.playlist = [];
		    // Make both arrays point to the same object elements. Gives us 2
		    // different arrays, each pointing to the same actual object. ie.,
		    // Not copies of the object.
		    $.each(this.original, function(i, v) {
			    self.playlist[i] = self.original[i];
		    });
	    },
	    _refresh: function(instant) {
		    /*
			 * instant: Can be undefined, true or a function. undefined -> use
			 * animation timings true -> no animation function -> use animation
			 * timings and excute function at half way point.
			 */
		    var self = this;
		    
		    if (instant && !$.isFunction(instant)) {
			    $(this.cssSelector.playlist + " ul").empty();
			    $.each(this.playlist, function(i, v) {
				    $(self.cssSelector.playlist + " ul").append(self._createListItem(self.playlist[i]));
			    });
			    this._updateControls();
		    } else {
			    var displayTime = $(this.cssSelector.playlist + " ul").children().length ? this.options.playlistOptions.displayTime
			            : 0;
			    
			    $(this.cssSelector.playlist + " ul").slideUp(displayTime, function() {
				    var $this = $(this);
				    $(this).empty();
				    
				    $.each(self.playlist, function(i, v) {
					    $this.append(self._createListItem(self.playlist[i]));
				    });
				    self._updateControls();
				    if ($.isFunction(instant)) {
					    instant();
				    }
				    if (self.playlist.length) {
					    $(this).slideDown(self.options.playlistOptions.displayTime);
				    } else {
					    $(this).show();
				    }
			    });
		    }
	    },
	    _createListItem: function(media) {
		    var self = this;
		    
		    // Wrap the <li> contents in a <div>
		    var listItem = "<li><div>";
		    
		    // Create remove control
		    listItem += "<a href='javascript:;' class='" + this.options.playlistOptions.removeItemClass
		            + "'>&times;</a>";
		    
		    // Create links to free media
		    if (media.free) {
			    var first = true;
			    listItem += "<span class='" + this.options.playlistOptions.freeGroupClass + "'>(";
			    $.each(media, function(property, value) {
				    if ($.jPlayer.prototype.format[property]) { // Check
					    // property is a
					    // media format.
					    if (first) {
						    first = false;
					    } else {
						    listItem += " | ";
					    }
					    listItem += "<a class='" + self.options.playlistOptions.freeItemClass + "' href='" + value
					            + "' tabindex='1'>" + property + "</a>";
				    }
			    });
			    listItem += ")</span>";
		    }
		    
		    // The title is given next in the HTML otherwise the float:right on
		    // the free media corrupts in IE6/7
		    duration = ("0" + parseInt(media.duration / 60, 10)).slice(-2) + ":" + ("0" + (media.duration % 60)).slice(-2);
		    listItem += "<a href='javascript:;' class='"
		            + this.options.playlistOptions.itemClass
		            + "' tabindex='1'>"
		            + media.title
		            + " <span class='jp-artist'>by {0} ({1}, {2}, {3}, {4}kbps, {5}) </span>".f(media.artist, media.album,
		                    media.track, media.year, media.bitrate, duration);
		    listItem += "</div></li>";
		    
		    return listItem;
	    },
	    _createItemHandlers: function() {
		    var self = this;
		    // Create .live() handlers for the playlist items
		    $(this.cssSelector.playlist + " a." + this.options.playlistOptions.itemClass).die("click").live("click",
		            function() {
			            var index = $(this).parent().parent().index();
			            var displayIndex = self._getDisplayedIndex(index);
			            if (self.current !== displayIndex) {
				            self.play(displayIndex);
			            } else {
				            $(self.cssSelector.jPlayer).jPlayer("play");
			            }
			            $(this).blur();
			            return false;
		            });
		    
		    // Create .live() handlers that disable free media links to force
		    // access via right click
		    $(self.cssSelector.playlist + " a." + this.options.playlistOptions.freeItemClass).die("click").live(
		            "click", function() {
			            $(this).parent().parent().find("." + self.options.playlistOptions.itemClass).click();
			            $(this).blur();
			            return false;
		            });
		    
		    // Create .live() handlers for the remove controls
		    $(self.cssSelector.playlist + " a." + this.options.playlistOptions.removeItemClass).die("click").live(
		            "click", function() {
			            var index = $(this).parent().parent().index();
			            self.remove(index);
			            $(this).blur();
			            return false;
		            });
	    },
	    _updateControls: function() {
		    if (this.options.playlistOptions.enableRemoveControls) {
			    $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).show();
		    } else {
			    $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).hide();
		    }
		    if (this.shuffled) {
			    $(this.cssSelector.shuffleOff).show();
			    $(this.cssSelector.shuffle).hide();
		    } else {
			    $(this.cssSelector.shuffleOff).hide();
			    $(this.cssSelector.shuffle).show();
		    }
	    },
	    _highlight: function(index) {
		    if (this.playlist.length && index !== undefined) {
			    $(this.cssSelector.playlist + " .jp-playlist-current").removeClass("jp-playlist-current");
			    $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").addClass("jp-playlist-current")
			            .find(".jp-playlist-item").addClass("jp-playlist-current");
			    $(this.cssSelector.title + " li").html(
			            this.playlist[index].title
			                    + (this.playlist[index].artist ? " <span class='jp-artist'>by "
			                            + this.playlist[index].artist + "</span>" : ""));
		    }
	    },
	    setPlaylist: function(playlist, instant) {
		    this._initPlaylist(playlist);
		    this._init(instant);
	    },
	    add: function(media, playNow) {
		    var that = this;
		    if ($.isArray(media)) {
			    $.each(media, function(i, track) {
				    that.add(track);
			    });
			    return;
		    }
		    // GAL - changed here to add to beginning of list
		    $(this.cssSelector.playlist + " ul").prepend(this._createListItem(media)).find("li:first-child").hide()
		            .slideDown(this.options.playlistOptions.addTime);
		    this._updateControls();
		    this.original.push(media);
		    this.playlist.push(media); // Both array elements share the same
		    // object pointer. Comforms with
		    // _initPlaylist(p) system.
		    
		    if (playNow) {
			    this.play(this.playlist.length - 1);
		    } else {
			    if (this.original.length === 1) {
				    this.select(0);
			    }
		    }
	    },
	    remove: function(index) {
		    var self = this;
		    
		    if (index === undefined) {
			    this._initPlaylist([]);
			    this._refresh(function() {
				    $(self.cssSelector.jPlayer).jPlayer("clearMedia");
			    });
			    return true;
		    } else {
			    
			    if (this.removing) {
				    return false;
			    } else {
				    index = (index < 0) ? self.original.length + index : index; // Negative
				    // index
				    // relates
				    // to
				    // end
				    // of
				    // array.
				    if (0 <= index && index < this.playlist.length) {
					    this.removing = true;
					    
					    $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").slideUp(
					            this.options.playlistOptions.removeTime,
					            function() {
						            $(this).remove();
						            
						            if (self.shuffled) {
							            var item = self.playlist[index];
							            $.each(self.original, function(i, v) {
								            if (self.original[i] === item) {
									            self.original.splice(i, 1);
									            return false; // Exit $.each
								            }
							            });
							            self.playlist.splice(index, 1);
						            } else {
							            self.original.splice(index, 1);
							            self.playlist.splice(index, 1);
						            }
						            
						            if (self.original.length) {
							            if (index === self.current) {
								            self.current = (index < self.original.length) ? self.current
								                    : self.original.length - 1; // To
								            // cope
								            // when
								            // last
								            // element
								            // being
								            // selected
								            // when
								            // it
								            // was
								            // removed
								            self.select(self.current);
							            } else if (index < self.current) {
								            self.current--;
							            }
						            } else {
							            $(self.cssSelector.jPlayer).jPlayer("clearMedia");
							            self.current = 0;
							            self.shuffled = false;
							            self._updateControls();
						            }
						            
						            self.removing = false;
					            });
				    }
				    return true;
			    }
		    }
	    },
	    select: function(index) {
		    index = (index < 0) ? this.original.length + index : index; // Negative
		    // index
		    // relates
		    // to
		    // end
		    // of
		    // array.
		    var displayIndex = this._getDisplayedIndex(index);
		    if (0 <= index && index < this.playlist.length) {
			    this.current = index;
			    this._highlight(displayIndex);
			    $(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]);
		    } else {
			    this.current = 0;
		    }
	    },
	    play: function(index) {
		    index = (index < 0) ? this.original.length + index : index; // Negative
		    // index
		    // relates
		    // to
		    // end
		    // of
		    // array.
		    if (0 <= index && index < this.playlist.length) {
			    if (this.playlist.length) {
				    this.select(index);
				    $(this.cssSelector.jPlayer).jPlayer("play");
			    }
		    } else if (index === undefined) {
			    $(this.cssSelector.jPlayer).jPlayer("play");
		    }
	    },
	    pause: function() {
		    $(this.cssSelector.jPlayer).jPlayer("pause");
	    },
	    next: function() {
		    var index = (this.current + 1 < this.playlist.length) ? this.current + 1 : 0;
		    if (this.loop) {
			    // See if we need to shuffle before looping to start, and only
			    // shuffle if more than 1 item.
			    if (index === 0 && this.shuffled && this.options.playlistOptions.shuffleOnLoop
			            && this.playlist.length > 1) {
				    this.shuffle(true, true); // playNow
			    } else {
				    this.play(index);
			    }
		    } else {
			    // The index will be zero if it just looped round
			    if (index > 0) {
				    this.play(index);
			    }
		    }
	    },
	    previous: function() {
		    var index = (this.current - 1 >= 0) ? this.current - 1 : this.playlist.length - 1;
		    
		    if (this.loop && this.options.playlistOptions.loopOnPrevious || index < this.playlist.length - 1) {
			    this.play(index);
		    }
	    },
	    shuffle: function(shuffled, playNow) {
		    var self = this;
		    
		    if (shuffled === undefined) {
			    shuffled = !this.shuffled;
		    }
		    
		    if (shuffled || shuffled !== this.shuffled) {
			    
			    $(this.cssSelector.playlist + " ul").slideUp(this.options.playlistOptions.shuffleTime, function() {
				    self.shuffled = shuffled;
				    if (shuffled) {
					    self.playlist.sort(function() {
						    return 0.5 - Math.random();
					    });
				    } else {
					    self._originalPlaylist();
				    }
				    self._refresh(true); // Instant
				    
				    if (playNow || !$(self.cssSelector.jPlayer).data("jPlayer").status.paused) {
					    self.play(0);
				    } else {
					    self.select(0);
				    }
				    
				    $(this).slideDown(self.options.playlistOptions.shuffleTime);
			    });
		    }
	    },
	    isLastSongPlaying: function() {
		    return this.current == this.playlist.length - 1;
	    },
	    currentPlayingSong: function() {
		    return this.playlist[this.current];
	    }
	};
})(jQuery);
